// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <benchmark/benchmark.h>

#include <string>
#include <type_traits>
#include <vector>
#include <vamos/vamos.h>
#include <nebula/testing/ktest_util.h>
#include <nebula/util/utf8_internal.h>

// Do not benchmark potentially inlined functions directly inside the benchmark loop
static TURBO_NOINLINE bool ValidateUTF8NoInline(const uint8_t* data, int64_t size) {
  return ::nebula::util::ValidateUTF8Inline(data, size);
}

static TURBO_NOINLINE bool ValidateAsciiNoInline(const uint8_t* data, int64_t size) {
  return vamos::validate_ascii(reinterpret_cast<const char*>(data), size);
}

namespace nebula {
namespace util {

static const char* tiny_valid_ascii = "characters";
static const char* tiny_valid_non_ascii = "caractères";

static const char* valid_ascii =
    "UTF-8 is a variable width character encoding capable of encoding all 1,112,064 "
    "valid code points in Unicode using one to four 8-bit bytes";
static const char* valid_almost_ascii =
    "UTF-8 est un codage de caractères informatiques conçu pour coder l’ensemble des "
    "caractères du « répertoire universel de caractères codés »";
static const char* valid_non_ascii =
    "UTF-8 はISO/IEC 10646 (UCS) "
    "とUnicodeで使える8ビット符号単位の文字符号化形式及び文字符号化スキーム。 ";

static std::string MakeLargeString(const std::string& base, int64_t nbytes) {
  int64_t nrepeats = (nbytes + base.size() - 1) / base.size();
  std::string s;
  s.reserve(nrepeats * nbytes);
  for (int64_t i = 0; i < nrepeats; ++i) {
    s += base;
  }
  return s;
}

static void BenchmarkUTF8Validation(
    benchmark::State& state,  // NOLINT non-const reference
    const std::string& s, bool expected) {
  auto data = reinterpret_cast<const uint8_t*>(s.data());
  auto data_size = static_cast<int64_t>(s.size());

  InitializeUTF8();
  bool b = ValidateUTF8NoInline(data, data_size);
  if (b != expected) {
    std::cerr << "Unexpected validation result" << std::endl;
    std::abort();
  }

  while (state.KeepRunning()) {
    bool b = ValidateUTF8NoInline(data, data_size);
    benchmark::DoNotOptimize(b);
  }
  state.SetBytesProcessed(state.iterations() * s.size());
}

static void BenchmarkASCIIValidation(
    benchmark::State& state,  // NOLINT non-const reference
    const std::string& s, bool expected) {
  auto data = reinterpret_cast<const uint8_t*>(s.data());
  auto data_size = static_cast<int64_t>(s.size());

  bool b = ValidateAsciiNoInline(data, data_size);
  if (b != expected) {
    std::cerr << "Unexpected validation result" << std::endl;
    std::abort();
  }

  while (state.KeepRunning()) {
    bool b = ValidateAsciiNoInline(data, data_size);
    benchmark::DoNotOptimize(b);
  }
  state.SetBytesProcessed(state.iterations() * s.size());
}

static void ValidateTinyAscii(benchmark::State& state) {  // NOLINT non-const reference
  BenchmarkASCIIValidation(state, tiny_valid_ascii, true);
}

static void ValidateTinyNonAscii(benchmark::State& state) {  // NOLINT non-const reference
  BenchmarkUTF8Validation(state, tiny_valid_non_ascii, true);
}

static void ValidateSmallAscii(benchmark::State& state) {  // NOLINT non-const reference
  BenchmarkASCIIValidation(state, valid_ascii, true);
}

static void ValidateSmallAlmostAscii(
    benchmark::State& state) {  // NOLINT non-const reference
  BenchmarkUTF8Validation(state, valid_almost_ascii, true);
}

static void ValidateSmallNonAscii(
    benchmark::State& state) {  // NOLINT non-const reference
  BenchmarkUTF8Validation(state, valid_non_ascii, true);
}

static void ValidateLargeAscii(benchmark::State& state) {  // NOLINT non-const reference
  auto s = MakeLargeString(valid_ascii, 100000);
  BenchmarkASCIIValidation(state, s, true);
}

static void ValidateLargeAlmostAscii(
    benchmark::State& state) {  // NOLINT non-const reference
  auto s = MakeLargeString(valid_almost_ascii, 100000);
  BenchmarkUTF8Validation(state, s, true);
}

static void ValidateLargeNonAscii(
    benchmark::State& state) {  // NOLINT non-const reference
  auto s = MakeLargeString(valid_non_ascii, 100000);
  BenchmarkUTF8Validation(state, s, true);
}

BENCHMARK(ValidateTinyAscii);
BENCHMARK(ValidateTinyNonAscii);
BENCHMARK(ValidateSmallAscii);
BENCHMARK(ValidateSmallAlmostAscii);
BENCHMARK(ValidateSmallNonAscii);
BENCHMARK(ValidateLargeAscii);
BENCHMARK(ValidateLargeAlmostAscii);
BENCHMARK(ValidateLargeNonAscii);

}  // namespace util
}  // namespace nebula
